<template>
  <view class="uni-forms">
    <form>
      <slot></slot>
    </form>
  </view>
</template>

<script>
import Validator from './validate.js'
import {
  deepCopy,
  getValue,
  isRequiredField,
  setDataValue,
  getDataValue,
  realName,
  isRealName,
  rawData,
  isEqual,
} from './utils.js'

// #ifndef VUE3
// 后续会慢慢废弃这个方法
import Vue from 'vue'
Vue.prototype.binddata = function (name, value, formName) {
  if (formName) {
    this.$refs[formName].setValue(name, value)
  } else {
    let formVm
    for (let i in this.$refs) {
      const vm = this.$refs[i]
      if (vm && vm.$options && vm.$options.name === 'uniForms') {
        formVm = vm
        break
      }
    }
    if (!formVm) return console.error('当前 uni-froms 组件缺少 ref 属性')
    formVm.setValue(name, value)
  }
}
// #endif
/**
 * Forms 表单
 * @description 由输入框、选择器、单选框、多选框等控件组成，用以收集、校验、提交数据
 * @tutorial https://ext.dcloud.net.cn/plugin?id=2773
 * @property {Object} rules	表单校验规则
 * @property {String} validateTrigger = [bind|submit|blur]	校验触发器方式 默认 submit
 * @value bind		发生变化时触发
 * @value submit	提交时触发
 * @value blur	  失去焦点时触发
 * @property {String} labelPosition = [top|left]	label 位置 默认 left
 * @value top		顶部显示 label
 * @value left	左侧显示 label
 * @property {String} labelWidth	label 宽度，默认 70px
 * @property {String} labelAlign = [left|center|right]	label 居中方式  默认 left
 * @value left		label 左侧显示
 * @value center	label 居中
 * @value right		label 右侧对齐
 * @property {String} errShowType = [undertext|toast|modal]	校验错误信息提示方式
 * @value undertext	错误信息在底部显示
 * @value toast			错误信息toast显示
 * @value modal			错误信息modal显示
 * @event {Function} submit	提交时触发
 * @event {Function} validate	校验结果发生变化触发
 */
export default {
  name: 'uniForms',
  emits: ['validate', 'submit'],
  options: {
    // #ifdef MP-TOUTIAO
    virtualHost: false,
    // #endif
    // #ifndef MP-TOUTIAO
    virtualHost: true,
    // #endif
  },
  props: {
    // 即将弃用
    value: {
      type: Object,
      default() {
        return null
      },
    },
    // vue3 替换 value 属性
    modelValue: {
      type: Object,
      default() {
        return null
      },
    },
    // 1.4.0 开始将不支持 v-model ，且废弃 value 和 modelValue
    model: {
      type: Object,
      default() {
        return null
      },
    },
    // 表单校验规则
    rules: {
      type: Object,
      default() {
        return {}
      },
    },
    //校验错误信息提示方式 默认 undertext 取值 [undertext|toast|modal]
    errShowType: {
      type: String,
      default: 'undertext',
    },
    // 校验触发器方式 默认 bind 取值 [bind|submit]
    validateTrigger: {
      type: String,
      default: 'submit',
    },
    // label 位置，默认 left 取值  top/left
    labelPosition: {
      type: String,
      default: 'left',
    },
    // label 宽度
    labelWidth: {
      type: [String, Number],
      default: '',
    },
    // label 居中方式，默认 left 取值 left/center/right
    labelAlign: {
      type: String,
      default: 'left',
    },
    border: {
      type: Boolean,
      default: false,
    },
  },
  provide() {
    return {
      uniForm: this,
    }
  },
  data() {
    return {
      // 表单本地值的记录，不应该与传如的值进行关联
      formData: {},
      formRules: {},
    }
  },
  computed: {
    // 计算数据源变化的
    localData() {
      const localVal = this.model || this.modelValue || this.value
      if (localVal) {
        return deepCopy(localVal)
      }
      return {}
    },
  },
  watch: {
    // 监听数据变化 ,暂时不使用，需要单独赋值
    // localData: {},
    // 监听规则变化
    rules: {
      handler: function (val, oldVal) {
        this.setRules(val)
      },
      deep: true,
      immediate: true,
    },
  },
  created() {
    // #ifdef VUE3
    let getbinddata = getApp().$vm.$.appContext.config.globalProperties.binddata
    if (!getbinddata) {
      getApp().$vm.$.appContext.config.globalProperties.binddata = function (
        name,
        value,
        formName,
      ) {
        if (formName) {
          this.$refs[formName].setValue(name, value)
        } else {
          let formVm
          for (let i in this.$refs) {
            const vm = this.$refs[i]
            if (vm && vm.$options && vm.$options.name === 'uniForms') {
              formVm = vm
              break
            }
          }
          if (!formVm) return console.error('当前 uni-froms 组件缺少 ref 属性')
          if (formVm.model) formVm.model[name] = value
          if (formVm.modelValue) formVm.modelValue[name] = value
          if (formVm.value) formVm.value[name] = value
        }
      }
    }
    // #endif

    // 子组件实例数组
    this.childrens = []
    // TODO 兼容旧版 uni-data-picker ,新版本中无效，只是避免报错
    this.inputChildrens = []
    this.setRules(this.rules)
  },
  methods: {
    /**
     * 外部调用方法
     * 设置规则 ，主要用于小程序自定义检验规则
     * @param {Array} rules 规则源数据
     */
    setRules(rules) {
      // TODO 有可能子组件合并规则的时机比这个要早，所以需要合并对象 ，而不是直接赋值，可能会被覆盖
      this.formRules = Object.assign({}, this.formRules, rules)
      // 初始化校验函数
      this.validator = new Validator(rules)
    },

    /**
     * 外部调用方法
     * 设置数据，用于设置表单数据，公开给用户使用 ， 不支持在动态表单中使用
     * @param {Object} key
     * @param {Object} value
     */
    setValue(key, value) {
      let example = this.childrens.find((child) => child.name === key)
      if (!example) return null
      this.formData[key] = getValue(
        key,
        value,
        (this.formRules[key] && this.formRules[key].rules) || [],
      )
      return example.onFieldChange(this.formData[key])
    },

    /**
     * 外部调用方法
     * 手动提交校验表单
     * 对整个表单进行校验的方法，参数为一个回调函数。
     * @param {Array} keepitem 保留不参与校验的字段
     * @param {type} callback 方法回调
     */
    validate(keepitem, callback) {
      return this.checkAll(this.formData, keepitem, callback)
    },

    /**
     * 外部调用方法
     * 部分表单校验
     * @param {Array|String} props 需要校验的字段
     * @param {Function} 回调函数
     */
    validateField(props = [], callback) {
      props = [].concat(props)
      let invalidFields = {}
      this.childrens.forEach((item) => {
        const name = realName(item.name)
        if (props.indexOf(name) !== -1) {
          invalidFields = Object.assign({}, invalidFields, {
            [name]: this.formData[name],
          })
        }
      })
      return this.checkAll(invalidFields, [], callback)
    },

    /**
     * 外部调用方法
     * 移除表单项的校验结果。传入待移除的表单项的 prop 属性或者 prop 组成的数组，如不传则移除整个表单的校验结果
     * @param {Array|String} props 需要移除校验的字段 ，不填为所有
     */
    clearValidate(props = []) {
      props = [].concat(props)
      this.childrens.forEach((item) => {
        if (props.length === 0) {
          item.errMsg = ''
        } else {
          const name = realName(item.name)
          if (props.indexOf(name) !== -1) {
            item.errMsg = ''
          }
        }
      })
    },

    /**
     * 外部调用方法 ，即将废弃
     * 手动提交校验表单
     * 对整个表单进行校验的方法，参数为一个回调函数。
     * @param {Array} keepitem 保留不参与校验的字段
     * @param {type} callback 方法回调
     */
    submit(keepitem, callback, type) {
      for (let i in this.dataValue) {
        const itemData = this.childrens.find((v) => v.name === i)
        if (itemData) {
          if (this.formData[i] === undefined) {
            this.formData[i] = this._getValue(i, this.dataValue[i])
          }
        }
      }

      if (!type) {
        console.warn('submit 方法即将废弃，请使用validate方法代替！')
      }

      return this.checkAll(this.formData, keepitem, callback, 'submit')
    },

    // 校验所有
    async checkAll(invalidFields, keepitem, callback, type) {
      // 不存在校验规则 ，则停止校验流程
      if (!this.validator) return
      let childrens = []
      // 处理参与校验的item实例
      for (let i in invalidFields) {
        const item = this.childrens.find((v) => realName(v.name) === i)
        if (item) {
          childrens.push(item)
        }
      }

      // 如果validate第一个参数是funciont ,那就走回调
      if (!callback && typeof keepitem === 'function') {
        callback = keepitem
      }

      let promise
      // 如果不存在回调，那么使用 Promise 方式返回
      if (!callback && typeof callback !== 'function' && Promise) {
        promise = new Promise((resolve, reject) => {
          callback = function (valid, invalidFields) {
            !valid ? resolve(invalidFields) : reject(valid)
          }
        })
      }

      let results = []
      // 避免引用错乱 ，建议拷贝对象处理
      let tempFormData = JSON.parse(JSON.stringify(invalidFields))
      // 所有子组件参与校验,使用 for 可以使用  awiat
      for (let i in childrens) {
        const child = childrens[i]
        let name = realName(child.name)
        const result = await child.onFieldChange(tempFormData[name])
        if (result) {
          results.push(result)
          // toast ,modal 只需要执行第一次就可以
          if (this.errShowType === 'toast' || this.errShowType === 'modal') break
        }
      }

      if (Array.isArray(results)) {
        if (results.length === 0) results = null
      }
      if (Array.isArray(keepitem)) {
        keepitem.forEach((v) => {
          let vName = realName(v)
          let value = getDataValue(v, this.localData)
          if (value !== undefined) {
            tempFormData[vName] = value
          }
        })
      }

      // TODO submit 即将废弃
      if (type === 'submit') {
        this.$emit('submit', {
          detail: {
            value: tempFormData,
            errors: results,
          },
        })
      } else {
        this.$emit('validate', results)
      }

      // const resetFormData = rawData(tempFormData, this.localData, this.name)
      let resetFormData = {}
      resetFormData = rawData(tempFormData, this.name)
      callback && typeof callback === 'function' && callback(results, resetFormData)

      if (promise && callback) {
        return promise
      } else {
        return null
      }
    },

    /**
     * 返回validate事件
     * @param {Object} result
     */
    validateCheck(result) {
      this.$emit('validate', result)
    },
    _getValue: getValue,
    _isRequiredField: isRequiredField,
    _setDataValue: setDataValue,
    _getDataValue: getDataValue,
    _realName: realName,
    _isRealName: isRealName,
    _isEqual: isEqual,
  },
}
</script>

<style lang="scss">
.uni-forms {
}
</style>
